import enum
import requests
import random
import hashlib
from common.xmltool import xml_to_dict, dict_to_xml
import time
from common import chars

######################################
# consts
######################################

class BB_RetCode(enum.Enum):
    """
    Base Bridge RetCode
    """
    OK = "Success"

    NETWORK_ERROR = "网络异常"
    WXSERVER_ERROR = "微信服务器异常"
    WXSERVER_NO_RESPONSE = "微信服务器无返回"
    GET_GLOBAL_ACCESS_TOKEN_ERROR = '获取全局access_token失败'
    GET_GLOBAL_JSAPI_TICKET_ERROR = '获取jsapiticket错误'
    REQUEST_ERROR = "请求异常"
    CONFIG_ERROR = "微信配置错误"
    WX_PAY_OUT_TRADE_NO_ERROR = "缺少统一支付接口必填参数out_trade_no"
    WX_PAY_BODY_ERROR = "缺少统一支付接口必填参数body"
    WX_PAY_TOTAL_FEE_ERROR = "缺少统一支付接口必填参数total_fee"
    WX_PAY_TRADE_TYPE_ERROR = "缺少统一支付接口必填参数trade_type"
    WX_PAY_OPENID_ERROR = "统一支付接口中，缺少必填参数openid！trade_type为JSAPI时，openid为必填参数"
    WX_PAY_PRODUCT_ID_ERROR = "统一支付接口中，缺少必填参数product_id！trade_type为JSAPI时，product_id为必填参数"
    WX_PAY_RESP_ERROR = "微信返回异常"
    WX_PAY_RESP_SIGN_ERROR = "微信返回签名错误"
    WX_PAY_RESP_PARAMETER_ERROR = "微信返回参数错误"
    FAIL = "FAIL"
    SUCCESS = "SUCCESS"

    TOKEN_INVALID = "token不可用"
    SERVER_ERROR = "服务器错误"


class WxApiUrls(enum.Enum):
    """
    Wx Api Urls
    """
    GET_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token"
    CREATE_MENU = "https://api.weixin.qq.com/cgi-bin/menu/create"
    CONNECT_OAUTH2 = "https://open.weixin.qq.com/connect/oauth2/authorize"
    CODE_EXCHANGE_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token"
    PULL_USER_INFO = "https://api.weixin.qq.com/sns/userinfo"
    PULL_BASE_USER_INFO = "https://api.weixin.qq.com/cgi-bin/user/info"
    UNIFIED_ORDER = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
    GET_JSAPI_TICKET = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket'



class WxTradeType(enum.Enum):

    JSAPI = "JSAPI"
    NATIVE = "NATIVE"
    APP = 'APP'


#####################
# Utils
#####################


def util_get_access_token(appid, secret, grant_type='client_credential'):
    """
    Get Access Token
    :param url:
    :param appid:
    :param secret:
    :param grant_type:
    :return:
    """
    print(appid, secret)
    req_url = '%s?grant_type=%s&appid=%s&secret=%s' % (WxApiUrls.GET_ACCESS_TOKEN.value, grant_type, appid, secret)

    r = requests.get(req_url)
    if r.status_code == 200:
        if r.text == '':
            return False, BB_RetCode.WXSERVER_NO_RESPONSE.name
        data = r.json()
        if 'errcode' in data and data['errcode'] != 0:
            return False, BB_RetCode.CONFIG_ERROR.name
        else:
            return True, data
    elif r.status_code == 500:
        return False, BB_RetCode.WXSERVER_ERROR.name
    elif r.status_code == 400:
        return False, BB_RetCode.REQUEST_ERROR.name
    elif r.status_code:
        return False, BB_RetCode.NETWORK_ERROR.name


def util_get_jsapi_ticket(access_token):
    """
    Get Access Token
    :param url:
    :param appid:
    :param secret:
    :param grant_type:
    :return:
    """
    req_url = '%s?access_token=%s&type=%s' % (WxApiUrls.GET_JSAPI_TICKET.value, access_token, 'jsapi')

    r = requests.get(req_url)
    if r.status_code == 200:
        if r.text == '':
            return False, BB_RetCode.WXSERVER_NO_RESPONSE.name, r.text
        data = r.json()
        if 'errcode' in data and data['errcode'] != 0:
            return False, BB_RetCode.CONFIG_ERROR.name, r.text
        else:
            return True, data, r.text
    elif r.status_code == 500:
        return False, BB_RetCode.WXSERVER_ERROR.name, r.text
    elif r.status_code == 400:
        return False, BB_RetCode.REQUEST_ERROR.name, r.text
    elif r.status_code:
        return False, BB_RetCode.NETWORK_ERROR.name, r.text


def get_connect_oauth2_url(wx_config, refcode=''):
    """
    Web Paeg Auth Get Code
    :param appid:
    :param redirect_uri:
    :param response_type:
    :param scope:
    :param state:
    :return:
    """
    from urllib.parse import quote
    # if refcode :
    #     return '%s?appid=%s&redirect_uri=%s&response_type=%s&scope=%s&state=%s#wechat_redirect' % (
    #         WxApiUrls.CONNECT_OAUTH2.value, # BaseURL
    #         WX['app_id'], # appid
    #         quote(WX['oauth2_share_redirect_uri']+'?refcode='+refcode), #redirect_uri
    #         WX['oauth2_response_type'], #code
    #         WX['oauth2_scope'], #scope
    #         WX['oauth2_state'] #state
    #     )
    # else:
    return '%s?appid=%s&redirect_uri=%s&response_type=%s&scope=%s&state=%s#wechat_redirect' % (
        WxApiUrls.CONNECT_OAUTH2.value, # BaseURL
        wx_config['app_id'], # appid
        quote(wx_config['oauth2_redirect_uri']+'?refcode='+refcode), #redirect_uri
        wx_config['oauth2_response_type'], #code
        wx_config['oauth2_scope'], #scope
        wx_config['oauth2_state'] #state
    )


def code_exchange_access_token(wx_config, code, grant_type="authorization_code"):
    """
    Code exchange access token
    :param code:
    :param grant_type:
    :return:
    """
    req_url = "%s?appid=%s&secret=%s&code=%s&grant_type=%s" % (
        WxApiUrls.CODE_EXCHANGE_ACCESS_TOKEN.value,
        wx_config['app_id'],  # appid
        wx_config['app_secret'], #secret
        code,
        grant_type,
    )
    r = requests.get(req_url)
    if r.status_code == 200:
        if r.text == '':
            return False, BB_RetCode.WXSERVER_NO_RESPONSE.name, r.text
        data = r.json()
        if 'errcode' in data and data['errcode'] != 0:
            return False, BB_RetCode.CONFIG_ERROR.name, r.text
        else:
            return True, data, ''
    elif r.status_code == 500:
        return False, BB_RetCode.WXSERVER_ERROR.name, r.text
    elif r.status_code == 400:
        return False, BB_RetCode.REQUEST_ERROR.name, r.text
    else:
        return False, BB_RetCode.NETWORK_ERROR.name, r.text


def pull_user_info(access_token, openid, lang='zh_CN'):
    """
    拉取用户信息
    :param access_token:
    :param openid:
    :param lang:
    :return:
    """
    req_url = "%s?access_token=%s&openid=%s&lang=%s " % (
        WxApiUrls.PULL_USER_INFO.value,
        access_token,  # access_token
        openid, #openid
        lang,
    )
    r = requests.get(req_url)
    if r.status_code == 200:
        if r.text == '':
            return False, BB_RetCode.WXSERVER_NO_RESPONSE.name, r.text
        data = r.json()
        if 'errcode' in data and data['errcode'] != 0:
            return False, BB_RetCode.CONFIG_ERROR.name, r.text
        else:
            return True, data, ''
    elif r.status_code == 500:
        return False, BB_RetCode.WXSERVER_ERROR.name, r.text
    elif r.status_code == 400:
        return False, BB_RetCode.REQUEST_ERROR.name, r.text
    else:
        return False, BB_RetCode.NETWORK_ERROR.name, r.text


def pull_base_user_info(global_access_token, openid, lang='zh_CN'):
    req_url = "%s?access_token=%s&openid=%s&lang=%s" % (
        WxApiUrls.PULL_BASE_USER_INFO.value,
        global_access_token,
        openid,
        lang
    )
    r = requests.get(req_url)
    if r.status_code == 200:
        if r.text == '':
            return False, BB_RetCode.WXSERVER_NO_RESPONSE.name, r.text
        data = r.json()
        if 'errcode' in data and data['errcode'] != 0:
            return False, BB_RetCode.CONFIG_ERROR.name, r.text
        else:
            return True, data, ''
    elif r.status_code == 500:
        return False, BB_RetCode.WXSERVER_ERROR.name, r.text
    elif r.status_code == 400:
        return False, BB_RetCode.REQUEST_ERROR.name, r.text
    else:
        return False, BB_RetCode.NETWORK_ERROR.name, r.text


def dict_to_url_params(data):
    s = ''
    for k, v in sorted(data.items()):
        if k != 'sign' and v != '':
            s += k + '=' + str(v) + '&'
    return s[:-1]


def make_sign(data, key, method=hashlib.md5, convert_upper=True):
    s = dict_to_url_params(data)

    if key:
        s += '&key=' + key
    if convert_upper:
        ss = method(s.encode()).hexdigest().upper()
    else:
        ss = method(s.encode()).hexdigest()
    print('---%s  %s---' % (s, ss))
    return ss


def check_sign(resp_data, key):
    if 'sign' not in resp_data.keys():
        return False
    if make_sign(resp_data, key) != resp_data['sign']:
        return False
    return True


def unified_order(wx_config, order_dict, timeout=6, extra={}):
    """
    统一下单
    :param order_dict:
    :param timeout:
    :return:
    """
    if not order_dict: order_dict = {}
    if 'out_trade_no' not in order_dict.keys():
        return False, BB_RetCode.WX_PAY_OUT_TRADE_NO_ERROR.name, BB_RetCode.WX_PAY_OUT_TRADE_NO_ERROR.value
    elif 'body' not in order_dict.keys():
        return False, BB_RetCode.WX_PAY_BODY_ERROR.name, BB_RetCode.WX_PAY_BODY_ERROR.value
    elif 'total_fee' not in order_dict.keys():
        return False, BB_RetCode.WX_PAY_TOTAL_FEE_ERROR.name, BB_RetCode.WX_PAY_TOTAL_FEE_ERROR.value
    elif 'trade_type' not in order_dict.keys():
        return False, BB_RetCode.WX_PAY_TRADE_TYPE_ERROR.name, BB_RetCode.WX_PAY_TRADE_TYPE_ERROR.value
    # // 检测必填参数
    if order_dict.get('trade_type') == WxTradeType.JSAPI.name and not order_dict.get('openid', None):
        return False, BB_RetCode.WX_PAY_OPENID_ERROR.name, BB_RetCode.WX_PAY_OPENID_ERROR.value
    if order_dict.get('trade_type') == WxTradeType.NATIVE.name and not order_dict.get('product_id', None):
        return False, BB_RetCode.WX_PAY_PRODUCT_ID_ERROR.name, BB_RetCode.WX_PAY_PRODUCT_ID_ERROR.value
    # // 关联参数
    order_dict['notify_url'] = wx_config['pay_notify_url']
    order_dict['appid'] = wx_config['app_id']
    order_dict['mch_id'] = wx_config['mch_id']
    order_dict['spbill_create_ip'] = extra.get('remote_addr', '1.1.1.1')
    order_dict['nonce_str'] = ''.join(random.sample(chars, 32))
    print(order_dict)
    sign = make_sign(order_dict, wx_config['pay_key'])
    order_dict['sign'] = sign

    xml_data = dict_to_xml(order_dict)
    print('console:', xml_data)
    r = requests.post(WxApiUrls.UNIFIED_ORDER.value, xml_data.encode('utf-8'), 'text/xml')
    r.encoding = 'utf-8'
    if r.status_code != 200:
        return False, BB_RetCode.NETWORK_ERROR.name, r.text
    print('console:', r.text)
    resp_xml = r.text
    resp_data = xml_to_dict(resp_xml)
    if resp_data['return_code'] != 'SUCCESS':
        return False, BB_RetCode.WX_PAY_RESP_ERROR.name, resp_xml
    if not check_sign(resp_data, wx_config['pay_key']):
        return False, BB_RetCode.WX_PAY_RESP_SIGN_ERROR.name, resp_xml
    return True, BB_RetCode.OK.name, resp_data


def get_apppay_parameters(wx_config, resp_data):
    """
    用于微信app支付
    resp_data: unified order 返回
    """
    if 'appid' not in resp_data.keys() or 'prepay_id' not in resp_data.keys() or resp_data['prepay_id'] == '':
        return False, BB_RetCode.WX_PAY_RESP_PARAMETER_ERROR.name, resp_data
    data = {}
    data['appid'] = resp_data['appid']
    data['partnerid'] = wx_config['mch_id']
    data['prepayid'] = resp_data['prepay_id']
    data['package'] = 'Sign=WXPay'
    data['noncestr'] = ''.join(random.sample(chars, 32))
    data['timestamp'] = str(int(time.time()))
    sign = make_sign(data, wx_config['pay_key'])
    data['sign'] = sign
    return True, BB_RetCode.OK.name, data

def get_jsapi_parameters(wx_config, resp_data):
    """
    /**
     * 用于微信公众后网页支付
     * 获取jsapi支付的参数
     * @param array $UnifiedOrderResult 统一支付接口返回的数据
     * @throws WxPayException
     *
     * @return json数据，可直接填入js函数作为参数
     */
    public function GetJsApiParameters($UnifiedOrderResult)
    {
        if(!array_key_exists("appid", $UnifiedOrderResult)
            || !array_key_exists("prepay_id", $UnifiedOrderResult)
            || $UnifiedOrderResult['prepay_id'] == "")
        {
            throw new WxPayException("参数错误");
        }
        $jsapi = new WxPayJsApiPay();
        $jsapi->SetAppid($UnifiedOrderResult["appid"]);
        $timeStamp = time();
        $jsapi->SetTimeStamp("$timeStamp");
        $jsapi->SetNonceStr(WxPayApi::getNonceStr());
        $jsapi->SetPackage("prepay_id=" . $UnifiedOrderResult['prepay_id']);
        $jsapi->SetSignType("MD5");
        $jsapi->SetPaySign($jsapi->MakeSign());
        $parameters = json_encode($jsapi->GetValues());
        return $parameters;
    }
    """
    if 'appid' not in resp_data.keys() or 'prepay_id' not in resp_data.keys() or resp_data['prepay_id'] == '':
        return False, BB_RetCode.WX_PAY_RESP_PARAMETER_ERROR.name, resp_data
    jsapi_data = {}
    jsapi_data['appId'] = resp_data['appid']
    jsapi_data['timeStamp'] = str(time.time())
    jsapi_data['nonceStr'] = ''.join(random.sample(chars, 32))
    jsapi_data['package'] = 'prepay_id=%s' % resp_data['prepay_id']
    jsapi_data['signType'] = "MD5"
    sign = make_sign(jsapi_data, wx_config['pay_key'])
    jsapi_data['paySign'] = sign
    return True, BB_RetCode.OK.name, jsapi_data


def get_jssdk_parameters(wx_config, jsapi_ticket, data):

    # Get JSAPI ticket

    data['noncestr'] = ''.join(random.sample(chars, 32))
    data['jsapi_ticket'] = jsapi_ticket
    data['timestamp'] = int(time.time())

    s1 = '&'.join(['%s=%s' % (key, data[key]) for key in sorted(data)])
    signature = hashlib.sha1(s1.encode()).hexdigest()
    # signature = make_sign(data, '', method=hashlib.sha1, convert_upper=False)
    del data['jsapi_ticket']
    data['appId'] = wx_config['app_id']
    data['signature'] = signature
    return True, BB_RetCode.OK.name, data


def process_pay_notify_callback(wx_config, data):
    """
    处理支付通知回调
    :param request:
    :return:
    """
    if type(data) == dict:
        resp_data = data
    elif '<xml>' in data:
        resp_data = xml_to_dict(data)
    else:
        return False, BB_RetCode.FAIL.name, data
    if resp_data['return_code'] != 'SUCCESS':
        return False, BB_RetCode.FAIL.name, data
    if not check_sign(resp_data, wx_config['pay_key']):
        return False, BB_RetCode.FAIL.name, data
    return True, BB_RetCode.SUCCESS.name, resp_data
